/**
   Copyright 2011 Фроловский А. В.

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
   
   This file is a part of MyStats project
 */

package foo.mystats.model;

import foo.mystats.db.DbOpenHelper;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Observable;
import java.util.TimeZone;

import org.achartengine.model.TimeSeries;
import org.achartengine.model.XYMultipleSeriesDataset;

import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;

public class StatisticsModel extends Observable {
	private Date veryFirstDate;
	private Date veryLastDate;
	private Date firstDate;
	private Date lastDate;
	private Row row;
	private Object minimum;
	private Object maximum;
	private Object average;
	private long totalNumberOfEntries;
	private long numberOfEntries;
	private Context context;

	public StatisticsModel(Context context, long rowId) {
		this.context = context;

		row = Row.getRowById(context, rowId);

		initDates();
		initMinMaxAvgValues();
	}

	/**
	 * Sets very first and last dates and total number of entries. Initial
	 * values of first and last dates are very first and very last accordingly.
	 */
	public void initDates() {
		totalNumberOfEntries = 0;
		veryFirstDate = new Date(0);
		veryLastDate = new Date(0);
		firstDate = veryFirstDate;
		lastDate = veryLastDate;

		if (row == null) {
			return;
		}

		DbOpenHelper dbOpenHelper = new DbOpenHelper(context);
		SQLiteDatabase db = dbOpenHelper.getWritableDatabase();

		Cursor c = null;
		try {
			c = db.rawQuery("select\r\n" + "    count(datetime),\r\n" + "    min(datetime),\r\n"
					+ "    max(datetime)\r\n" + "from\r\n" + "    data\r\n" + "where\r\n"
					+ "    row_id = ?", new String[] { Long.toString(row.getId()) });
			c.moveToFirst();

			totalNumberOfEntries = c.getLong(0);
			numberOfEntries = totalNumberOfEntries;

			veryFirstDate = new Date(c.getLong(1) * 1000);
			veryLastDate = new Date(c.getLong(2) * 1000);

			firstDate = getDateOnly(veryFirstDate);
			lastDate = getDateOnly(veryLastDate);
		} finally {
			if (c != null) {
				c.close();
			}
			if (db != null) {
				db.close();
			}
		}
	}

	public Date getDateOnly(Date dateTime) {
		GregorianCalendar gc = new GregorianCalendar();
		gc.setTimeZone(TimeZone.getTimeZone("GMT+00:00"));

		gc.setTime(dateTime);
		gc.set(Calendar.HOUR_OF_DAY, 0);
		gc.set(Calendar.MINUTE, 0);
		gc.set(Calendar.SECOND, 0);
		gc.set(Calendar.MILLISECOND, 0);

		return gc.getTime();
	}

	public void initNumberOfEntries() {
		numberOfEntries = totalNumberOfEntries;

		if (row == null) {
			return;
		}

		DbOpenHelper dbOpenHelper = new DbOpenHelper(context);
		SQLiteDatabase db = dbOpenHelper.getWritableDatabase();

		Cursor c = null;
		try {
			c = db.rawQuery(
					"select\r\n" + "    count(datetime)\r\n" + "from\r\n" + "    data\r\n"
							+ "where\r\n" + "    row_id = ? and\r\n"
							+ "    datetime between ? and ?",
					new String[] { Long.toString(row.getId()),
							Long.toString(firstDate.getTime() / 1000),
							Long.toString((lastDate.getTime() / 1000) + 86400) });
			c.moveToFirst();

			numberOfEntries = c.getLong(0);
		} finally {
			if (c != null) {
				c.close();
			}
			if (db != null) {
				db.close();
			}
		}
	}

	public void initMinMaxAvgValues() {
		minimum = null;
		maximum = null;
		average = null;

		if (row == null || getNumberOfEntries() == 0) {
			return;
		}

		DbOpenHelper dbOpenHelper = new DbOpenHelper(context);
		SQLiteDatabase db = dbOpenHelper.getWritableDatabase();

		Cursor c = null;
		try {
			c = db.rawQuery(
					"select\r\n" + "    case r.data_type\r\n"
							+ "        when 1 then min(value)\r\n"
							+ "        when 2 then min(value)\r\n"
							+ "        when 5 then min(value)\r\n" + "        else null\r\n"
							+ "    end minimum,\r\n" + "    case r.data_type\r\n"
							+ "        when 1 then max(value)\r\n"
							+ "        when 2 then max(value)\r\n"
							+ "        when 5 then max(value)\r\n" + "        else null\r\n"
							+ "    end maxmimum,\r\n" + "    case r.data_type\r\n"
							+ "        when 1 then avg(value)\r\n"
							+ "        when 2 then avg(value)\r\n"
							+ "        when 5 then avg(value)\r\n" + "        else null\r\n"
							+ "    end average\r\n" + "from\r\n" + "    data d,\r\n"
							+ "    row r\r\n" + "where\r\n" + "    d.row_id = r.id and\r\n"
							+ "    r.id = ? and\r\n" + "    d.datetime between ? and ?",
					new String[] { Long.toString(row.getId()),
							Long.toString(firstDate.getTime() / 1000),
							Long.toString((lastDate.getTime() / 1000) + 86400) });
			c.moveToFirst();

			minimum = Data.getDataObjectFromCursor(c, 0, row.getDataType().getType());
			maximum = Data.getDataObjectFromCursor(c, 1, row.getDataType().getType());
			average = Data.getDataObjectFromCursor(c, 2, row.getDataType().getType());

		} finally {
			if (c != null) {
				c.close();
			}
			if (db != null) {
				db.close();
			}
		}
	}

	public Date getFirstDate() {
		return firstDate;
	}

	public void setFirstDate(Date date) {
		firstDate = getDateOnly(date);

		initNumberOfEntries();
		initMinMaxAvgValues();

		setChanged();
		notifyObservers();
	}

	public Date getLastDate() {
		return lastDate;
	}

	public void setLastDate(Date date) {
		lastDate = getDateOnly(date);

		initNumberOfEntries();
		initMinMaxAvgValues();

		setChanged();
		notifyObservers();
	}

	public Date getVeryFirstDate() {
		return veryFirstDate;
	}

	public Date getVeryLastDate() {
		return veryLastDate;
	}

	public long getTotalNumberOfEntries() {
		return totalNumberOfEntries;
	}

	public Object getMinimum() {
		return minimum;
	}

	public Object getMaximum() {
		return maximum;
	}

	public Object getAverage() {
		return average;
	}

	public long getNumberOfEntries() {
		return numberOfEntries;
	}

	public int getDaysCount() {
		if (getLastDate().getTime() >= getFirstDate().getTime()) {
			return 1 + (int) Math.round(((double) (Math.abs(getLastDate().getTime()
					- getFirstDate().getTime())))
					/ (86400 * 1000));
		} else {
			return 0;
		}
	}

	public double getEntriesPerDay() {
		int daysCount = getDaysCount();

		if (daysCount != 0) {
			return (double) getNumberOfEntries() / daysCount;
		} else {
			return 0;
		}
	}

	public void setMaximumPeriod() {
		firstDate = veryFirstDate;
		lastDate = veryLastDate;

		setChanged();
		notifyObservers();
	}

	public XYMultipleSeriesDataset getDataSet() {
		if (row.getDataType().getType() == DataType.STRING) {
			return null;
		}

		XYMultipleSeriesDataset dataset = new XYMultipleSeriesDataset();

		ArrayList<Data> result = new ArrayList<Data>();

		DbOpenHelper dbOpenHelper = new DbOpenHelper(context);
		SQLiteDatabase db = dbOpenHelper.getWritableDatabase();

		Cursor c = null;
		try {
			c = db.rawQuery(
					"select\r\n" + "    value,\r\n" + "    datetime\r\n" + "from\r\n"
							+ "    data\r\n" + "where\r\n" + "    row_id = ? and\r\n"
							+ "    datetime between ? and ?\r\n" + "order by\r\n" + "    datetime",
					new String[] { Long.toString(row.getId()),
							Long.toString(firstDate.getTime() / 1000),
							Long.toString((lastDate.getTime() / 1000) + 86400) });

			if (c.moveToFirst()) {
				do {
					Data d = new Data(0, new Date((long) c.getInt(1) * 1000),
							Data.getDataObjectFromCursor(c, 0, row.getDataType().getType()));

					result.add(d);
				} while (c.moveToNext());
			} else {
				// there is no data
				return null;
			}
		} finally {
			if (c != null) {
				c.close();
			}
			if (db != null) {
				db.close();
			}
		}

		TimeSeries series = new TimeSeries(row.getName());

		for (Data d : result) {
			switch (row.getDataType().getType()) {
			case DataType.INTEGER:
				series.add(d.getDateTime(),
						Double.valueOf((double) ((Integer) d.getValue()).intValue()));
				break;
			case DataType.REAL:
				series.add(d.getDateTime(), ((Double) d.getValue()).doubleValue());
				break;
			case DataType.TIME:
				series.add(d.getDateTime(),
						Double.valueOf((double) ((Date) d.getValue()).getTime() / (3600 * 1000)));
				break;
			case DataType.BOOLEAN:
				series.add(d.getDateTime(),
						Double.valueOf((double) (((Boolean) d.getValue()).booleanValue() ? 1 : 0)));
				break;
			}
		}

		dataset.addSeries(series);

		return dataset;
	}
}
